/*

 Copyright 2012 Peter Dornbach.

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

     http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
   
*/

package com.dornbachs.zebra.guitarear;

import java.util.TreeMap;
import java.util.Vector;

/*
 * Strums:
 * 
 * RUST:
 * 
 * D x D x | D x D x
 * D x D x | D U D x
 * D x D x | D U D U
 * D x D x | D U x x
 * D x D U | x U D x	
 * 
 * (3: Wild things like)
 * D1 x D1 x | x U2 D2 U2 | D3 x D3 x | x U2 D2 U2 |
 * (3: All along the watchtower)
 * D1 U1 D1 x | x U2 D2 U2 | D3 U3 D3 x | x U2 D2 U2 |
 * (4: Mr Jones)
 * D1 x D1 x | D1 U1 D1 U2 | x x D2 x | D2 U2 D2 U3 | x x D3 x | D3 U3 D3 U4 | x x D4 x | D4 U4 D4 U4 |
 * 
 */

public class Strumming {
	// TODO: support different strummings
	public static final Riff strum(int strumLevel, Vector<ChordShape> chords) {
		final float BEATS_PER_PATTERN = 0.5f;
		
		String chordPattern = randomChordPattern(strumLevel, chords.size())._pattern;
		String strummingPattern = randomStrummingPattern(strumLevel)._pattern; 
		int l = Util.leastCommonMultiple(chordPattern.length(), strummingPattern.length());
		chordPattern = Util.concat(chordPattern, l / chordPattern.length());
		strummingPattern = Util.concat(strummingPattern, l / strummingPattern.length());

		Riff riff = new Riff(BPM[strumLevel]);
		float lastBeat = 0.0f;
		for (int i = 0; i < chordPattern.length(); ++i) {
			if (strummingPattern.charAt(i) == ' ') {
				continue;
			}
			lastBeat = i * BEATS_PER_PATTERN;
			riff.advance(lastBeat);
			if (strummingPattern.charAt(i) == 'x') {
				riff.mute();
			} else {
				riff.strumChord(chords.get(chordPattern.charAt(i) - '1'));
			}
		}
		riff.advance(lastBeat + 4.0f * BEATS_PER_PATTERN);
		riff.mute();
		return riff;
	}
  
  private static class StrummingPattern {
  	public StrummingPattern(String pattern) {
  		_pattern = pattern;
  	}

    public String _pattern;
  }
  
  private static final StrummingPattern[][] STRUMMING_PATTERNS = {
  	{
  		// Level0
  		new StrummingPattern("D "),
  	},
  	{
  		// Level1
	  	new StrummingPattern("D D DUD "),
	  	new StrummingPattern("D DU UD "),
	  	new StrummingPattern("DUD  UDU"),
	  	new StrummingPattern("D D  UDU"),
	  	new StrummingPattern("D D DUDU"),
	  	new StrummingPattern("D DUD DU"),
  	},
  	{
  		// Level2
	  	new StrummingPattern("D DxD Dx"),
	  	new StrummingPattern("D DxDUDx"),
	  	new StrummingPattern("DUDxDUDx"),
	  	new StrummingPattern("D DUxUDx"),
  	},
  };
  private static final int MAX_STRUMMING_LEVEL = STRUMMING_PATTERNS.length - 1;
  
  private static final StrummingPattern randomStrummingPattern(int level) {
  	level = Math.min(level, MAX_STRUMMING_LEVEL);
  	return STRUMMING_PATTERNS[level][Util.random(STRUMMING_PATTERNS[level].length)];
  }
  
  private static class ChordPattern {
  	public ChordPattern(String pattern) {
  		_pattern = pattern;
  	}
  	
  	public int numChords() {
  		int maxChord = 0;
  		for (int i = 0; i < _pattern.length(); ++i) {
  			int chord = _pattern.charAt(i) - '0';
  			if (chord > maxChord) {
  				maxChord = chord;
  			}
  		}
  		return maxChord;
  	}
  	
  	public String _pattern;
  }
  
  private static final ChordPattern[][] CHORD_PATTERNS = {
  	{
  		// Level0
	  	new ChordPattern("11"),
	  	new ChordPattern("1122"),
	  	new ChordPattern("112233"),
	  	new ChordPattern("11223344"),
  	},
  	{
  		// Level1
	  	new ChordPattern("1111"),
	  	new ChordPattern("11112222"),
	  	new ChordPattern("11222211"),
	  	new ChordPattern("1111222211112222"),
	  	new ChordPattern("1111222233332222"),
	  	new ChordPattern("1111222233331111"),
	  	new ChordPattern("1111111122223333"),
	  	new ChordPattern("1111222233333333"),
	  	new ChordPattern("1111222233334444"),
  	},
  };
  private static final int MAX_CHORD_PATTERN_LEVEL = CHORD_PATTERNS.length - 1;
  
  // First by level, then by numChords
  private static final ChordPattern[][][] CHORD_PATTERN_MAP;
  
  static {
  	CHORD_PATTERN_MAP = new ChordPattern[CHORD_PATTERNS.length][][];
  	for (int i = 0; i < CHORD_PATTERNS.length; ++i) {
  		TreeMap<Integer, Vector<ChordPattern>> map = new TreeMap<Integer, Vector<ChordPattern>>();
    	for (int j = 0; j < CHORD_PATTERNS[i].length; ++j) {
    		int numChords = CHORD_PATTERNS[i][j].numChords();
    		Vector<ChordPattern> chords = map.get(numChords);
    		if (chords == null) {
    			chords = new Vector<ChordPattern>();
    			map.put(numChords, chords);
    		}
    		chords.add(CHORD_PATTERNS[i][j]);
    	}
    	ChordPattern[][] patternForNumChords = new ChordPattern[map.lastKey()][];
  		CHORD_PATTERN_MAP[i] = patternForNumChords;
    	for (int j = 0; j < patternForNumChords.length; ++j) {
    		// We assume that for each numChords there is an entry.
    		ChordPattern[] patterns = new ChordPattern[map.get(j + 1).size()];
    		patterns = map.get(j + 1).toArray(patterns);
    		patternForNumChords[j] = patterns;
    	}
  	}
  }
  
  private static final ChordPattern randomChordPattern(int level, int numChords) {
  	level = Math.min(level, MAX_CHORD_PATTERN_LEVEL);
  	return CHORD_PATTERN_MAP[level][numChords - 1][
  	    Util.random(CHORD_PATTERN_MAP[level][numChords - 1].length)];
  }
  
  private static final int BPM[] = {
  	100,
  	140,
  	140,
  };
}
